﻿using NLog;
using SqlSugar;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using TiaoTiaoCode.NLogger;
using TiaoTiaoCode.NLogger.Dtos;
using TiaoTiaoCode.NLogger.Enums;

namespace TiaoTiaoCode.BasicDbCore
{
    public sealed class DbMultiFramework
    {
        #region 连接信息

        /// <summary>
        /// 数据库连接信息
        /// </summary>
        public static Dictionary<string, DbMultiItem> DicConnection { get; private set; }

        /// <summary>
        /// 是否使用日志
        /// </summary>
        public static bool UseFileLog { get; set; } = true;

        /// <summary>
        /// 是否使用数据库日志
        /// </summary>
        public static bool UseDbLog { get; private set; } = false;

        #endregion

        static DbMultiFramework()
        {
            DicConnection = new Dictionary<string, DbMultiItem>();
            _dicSugars = new Dictionary<string, ISqlSugarClient>();
        }

        private static Dictionary<string, ISqlSugarClient>? _dicSugars;

        /// <summary>
        /// SqlSugar对象索引器        
        /// </summary>
        /// <param name="connectId">连接识别Id</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static ISqlSugarClient Scope(string connectId)
        {
            lock ((_dicSugars as ICollection)!.SyncRoot)
            {
                if (_dicSugars.ContainsKey(connectId))
                    return _dicSugars[connectId];

                return default!;
            }
        }

        public static void SetScope(string connectId)
        {
            lock ((_dicSugars as ICollection)!.SyncRoot)
            {
                if (_dicSugars.ContainsKey(connectId)) return;

                if (!DicConnection.ContainsKey(connectId))
                    throw new Exception($"未设置【{connectId}】的连接信息");

                var cfg = DicConnection[connectId];
                var client = new SqlSugarScope(new ConnectionConfig
                {
                    ConfigId = connectId,
                    DbType = cfg.DbType,
                    ConnectionString = cfg.ConnectionString,
                    IsAutoCloseConnection = true,
                    AopEvents = new AopEvents
                    {
                        OnLogExecuting = //OnLogExcuting,
                            (sql, pars) =>
                            {
                                OnLogExcuting(cfg, sql, pars);
                            },
                        OnDiffLogEvent = //DiffLogAct
                            item =>
                            {
                                DiffLogAct(cfg, item);
                            }
                    },
                    ConfigureExternalServices = new ConfigureExternalServices
                    {
                        EntityService = (c, p) =>
                        {
                            if (!string.IsNullOrWhiteSpace(p.DefaultValue))
                            {
                                var tmp = p.DefaultValue;
                                p.DefaultValue = string.Empty;
                            }
                        }
                    }
                });

                cfg.SetUseDbLog();
                cfg.InitLog();

                _dicSugars.Add(connectId, client);
            }
        }

        #region 内部日志

        /// <summary>
        /// sql日志
        /// </summary>
        /// <param name="loggerName"></param>
        /// <param name="sql"></param>
        /// <param name="pars"></param>
        static void OnLogExcuting(DbMultiItem cfg, string sql, SugarParameter[] pars)
        {
            var sqlSentence = UtilMethods.GetNativeSql(sql, pars);
            if (cfg.UseFileLog)
                TiaoTiaoNLogger.LogDebug(sqlSentence, cfg.LoggerName);
        }

        /// <summary>
        /// 数据变化写入日志表
        /// </summary>
        /// <param name="item"></param>
        static void DiffLogAct(DbMultiItem cfg, DiffLogModel item)
        {
            var bfData = item.BeforeData;
            var afData = item.AfterData;

            if (item.DiffType == DiffType.insert)
            {
                var afDataColumn = afData[0].Columns;

                var log = new DbLogItem
                {
                    Id = SnowFlakeSingle.instance.NextId().ToString(),
                    Method = item.DiffType.ToString(),
                    TableName = afData.First().TableName,
                    OperateType = item.DiffType.ToString(),
                    LogData = afDataColumn.Aggregate("", (a, b) => a += $"{b.ColumnName}=>{b.Value},").TrimEnd(',')
                };

                TiaoTiaoNLogger.LogDb(log, cfg.LoggerName);
            }
            else if (item.DiffType == DiffType.update)
            {
                if (bfData.Count <= 0) return;
                var bfDataColumn = bfData[0].Columns;
                var afDataColumn = afData.Count <= 0 ? null : afData[0].Columns;

                var diffs = new List<string>
                {
                    $"主键：【{bfDataColumn.Where(t => t.IsPrimaryKey).First().Value}】"
                };

                bfDataColumn.ForEach(bf =>
                {
                    if (afDataColumn == null) return;
                    var af = afDataColumn.Where(t => t.ColumnName == bf.ColumnName).FirstOrDefault();
                    if (af == null) return;

                    if (bf.ColumnName.ToUpper() == "UpdateTime".ToUpper()) return;

                    switch (bf.Value.GetType().Name.ToUpper())
                    {
                        case "STRING":
                            if (af.Value.ToString() == bf.Value.ToString()) return;
                            break;
                        case "INT32":
                            if (int.TryParse(af.Value.ToString(), out var a)
                                && int.TryParse(bf.Value.ToString(), out var b))
                                if (a == b) return;
                            break;
                        case "DATETIME":
                            if (DateTime.TryParse(af.Value.ToString(), out var dta)
                                && DateTime.TryParse(bf.Value.ToString(), out var dtb))
                                if (dta == dtb) return;
                            break;
                        case "DECIMAL":
                            if (decimal.TryParse(af.Value.ToString(), out var da)
                                && decimal.TryParse(bf.Value.ToString(), out var db))
                                if (da == db) return;
                            break;
                    }

                    if (bf.Value != af.Value)
                        diffs.Add($"{bf.ColumnName}:【{bf.Value}】->【{af.Value}】");
                });

                if (diffs.Count > 0)
                {
                    var log = new DbLogItem
                    {
                        Id = SnowFlakeSingle.instance.NextId().ToString(),
                        Method = item.DiffType.ToString(),
                        TableName = bfData.First().TableName,
                        OperateType = "update",
                        LogData = diffs.Aggregate("", (a, b) => a += $"{b},").TrimEnd(',')
                    };


                    if (UseDbLog)
                        TiaoTiaoNLogger.LogDb(log, cfg.LoggerName);
                }
            }
            else if (item.DiffType == DiffType.delete)
            {
                if (bfData.Count <= 0) return;

                var bfDataColumn = bfData[0].Columns;
                var log = new DbLogItem
                {
                    Id = SnowFlakeSingle.instance.NextId().ToString(),
                    Method = item.DiffType.ToString(),
                    TableName = bfData.First().TableName,
                    OperateType = "delete",
                    LogData = bfDataColumn.Aggregate("", (a, b) => a += $"{b.ColumnName}=>{b.Value},").TrimEnd(',')
                };

                if (UseDbLog)
                    TiaoTiaoNLogger.LogDb(log, cfg.LoggerName);
            }
        }

        #endregion

        #region DbCore 初始化

        /// <summary>
        /// dbcore 初始化
        /// </summary>
        /// <param name="assemblyname">数据库表对象所在程序集名称</param>
        /// <param name="entitiesnamespace">数据库表对象所有命名空间</param>
        /// <param name="createdatabase">是否创建数据库</param>
        public static void AddDbCoreStartup(string connectId, string assemblyname, string entitiesnamespace, bool createdatabase = true)
        {
            SetScope(connectId);
            var scope = Scope(connectId);
            if (createdatabase)
                scope.DbMaintenance.CreateDatabase();

            var types = Assembly.Load(assemblyname).GetTypes()
                            .Where(x => x.FullName!.Contains(entitiesnamespace))
                            .ToArray();

            scope.CodeFirst.InitTables(types);
        }

        /// <summary>
        /// dbcore 初始化
        /// </summary>
        /// <param name="assemblyname">数据库表对象所在程序集名称</param>
        /// <param name="entitiesnamespace">数据库表对象所有命名空间</param>
        /// <param name="createdatabase">是否创建数据库</param>
        public static void AddDbCoreStartupByFile(string connectId, string assemblyname, string entitiesnamespace, bool createdatabase = true)
        {
            SetScope(connectId);
            var scope = Scope(connectId);
            if (createdatabase)
                scope.DbMaintenance.CreateDatabase();

            var types = Assembly.LoadFrom(assemblyname).GetTypes()
                            .Where(x => x.FullName!.Contains(entitiesnamespace))
                            .ToArray();

            scope.CodeFirst.InitTables(types);
        }

        #endregion
    }

    /// <summary>
    /// 多数据库连接时，每个连接配置内容
    /// </summary>
    public class DbMultiItem
    {
        /// <summary>
        /// 连接名，对应SqlSugar的ConnectId
        /// </summary>
        public string ConnectName { get; }

        /// <summary>
        /// 日志名
        /// </summary>
        public string LoggerName { get; }

        /// <summary>
        /// 数据库类型
        /// </summary>
        public DbType DbType { get; }

        /// <summary>
        /// 数据库连接串
        /// </summary>
        public string ConnectionString { get; }

        /// <summary>
        /// 是否使用数据库日志
        /// </summary>
        public bool UseDbLog { get; }

        /// <summary>
        /// 是否使用文件日志
        /// </summary>
        public bool UseFileLog { get; }

        public DbMultiItem(string connectName, string loggerName, DbType dbType, string connectionString, bool useFileLog = true, bool useDbLog = false)
        {
            ConnectName = connectName;
            LoggerName = loggerName;
            DbType = dbType;
            ConnectionString = connectionString;
            UseFileLog = useFileLog;
            UseDbLog = useDbLog;
        }
    }

    /// <summary>
    /// DbMultiItem 扩展
    /// </summary>
    public static class DbMultiItemExtension
    {
        /// <summary>
        /// 设置启用操作日志到数据库
        /// </summary>
        /// <param name="logTableName">日志表名</param>
        public static void SetUseDbLog(this DbMultiItem item, string logTableName = "sys_log")
        {
            if (!item.UseDbLog) return;

            var nlogDbType = item.DbType switch
            {
                DbType.Sqlite => NLogDbType.Sqlite,
                DbType.MySql => NLogDbType.MySql,
                DbType.SqlServer => NLogDbType.SqlServer,
                _ => NLogDbType.SqlServer
            };

            TiaoTiaoNLogger.AddDatabaseTarget(item.LoggerName,
                        nlogDbType,
                        item.ConnectionString,
                        $@"insert into {logTableName}(id,log_data,method,table_name,log_date,operate_type) values(@Id,@LogData,@Method,@TableName,@LogDate,@OperateType)",
                        typeof(DbLogItem));

            TiaoTiaoNLogger.SetLogger(item.LoggerName);
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="item"></param>
        public static void InitLog(this DbMultiItem item)
        {
            TiaoTiaoNLogger.AddFileTarget(item.LoggerName, "dbDebug", LogLevel.Debug, item.DbType.ToString());

            TiaoTiaoNLogger.SetLogger(item.LoggerName);
        }
    }
}
